﻿package {
	import Box2D.Common.Math.b2Vec2;
	
	import at.geoathome.text.TextFieldGenerator;
	import at.geoathome.utils.DevUtils;
	import at.geoathome.utils.FPS;
	import at.geoathome.utils.MEMORY;
	import at.geoathome.utils.loader.AssetLoader;
	
	import br.com.stimuli.loading.BulkLoader;
	
	import gamestates.*;
	
	import views.*;
	
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.system.ApplicationDomain;
	import flash.system.LoaderContext;
	import flash.text.TextField;
	import flash.ui.Keyboard;
	
	import Box2D.Dynamics.b2Body;	

	/**
	 * ...
	 * @author Georg Kamptner
	 * ToDo, add Game Body ad userData into box2d bodies.
	 * 		- Gewinnbedingung nicht nacht unten gehen lassen
	 */
	public class Game extends Sprite 
	{
		// -- fields & constants -------------------------------------------------------------------
		private var _asset_loader:AssetLoader;
		private var _asset_loader_context:LoaderContext;
		private var _world:GameWorld;
		private var _players:Array;
		private var _current_player:Player;
		private var _state:IGameState;
		private var _game_states:Object;
		private var _status_tf:TextField;
		
		private var _intro_screen_view:IntroView;
		private var _start_screen_view:StartScreenView;
		private var _settings_screen_view:GameSettingsView;
		private var _game_selection_view:GameSelectionView; 
		private var _in_game_view:InGameView;
		private var _game_type:String;
		private var _is_paused:Boolean;
		
		private static const DEBUG:Boolean = false;
		
		
		public static const START_SCREEN:String = "START_SCREEN";
		public static const GAME_SELECTION:String = "GAME_SELECTION";
		public static const SETTINGS_SCREEN:String = "SETTINGS_SCREEN";
		public static const DEFAULT:String = "DEFAULT";
		public static const ADD_BODY:String = "ADD_BODY";
		public static const MODIFY_BODIES:String = "MODIFY_BODIES";
		public static const INTRO_SCREEN:String ="INTRO_SCREEN";
		public static const GAME_TYPE_A:String = "GAME_TYPE_A";
		public static const GAME_TYPE_B:String = "GAME_TYPE_B";
		public static const GAME_TYPE_UNKNOWN:String = "GAME_TYPE_UNKNOWN";
		
		// -- getter & setter ---------------------------------------------------------------------
		public function get state():IGameState { return _state; }
		
		public function set state(value:IGameState):void 
		{
			_state = value;
		}
		public function get Player1Name() : String { 
			 return _players[0].name;
		}
		public function get Player2Name() : String { return _players[1].name;}
		
		public function get Player2TopHeight(): Number { return _players[1].towerheight;}
		
		public function get Player1TopHeight(): Number { return _players[0].towerheight;}
		
		public function get Player2TopWidth(): Number { return _players[1].towertopwidth;}
		
		public function get Player1TopWidth(): Number { return _players[0].towertopwidth;}
		
		public function get world():GameWorld { return _world; }
		
		public function get asset_loader():AssetLoader { return _asset_loader; }
		
		public function get game_type():String { return _game_type; }
		
		public function get current_player():Player { return _current_player;}
		
		public function get next_player():Player { 
			var ret:Player;
			if (_current_player.id == 0)
				ret = _players[1];
			else
				ret = _players[0];
			return ret;
		}

		public function get is_paused():Boolean { return _is_paused; }
		
		// -- constructor & init -------------------------------------------------------------------
		public function Game():void 
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			_is_paused = true;
			_game_type = GAME_TYPE_UNKNOWN;
			
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(e:Event = null):void 
		{	
			removeEventListener(Event.ADDED_TO_STAGE, init);
			
			// load intro
			// ...
			
			// startup
			startUp();
		}
		
		private function startUp():void
		{
			// entry point
			
			// for debugging only
			if (DEBUG)
			{
				var fps:FPS = new FPS();
				stage.addChild(fps);
				
				var mem:MEMORY = new MEMORY();
				stage.addChild(mem);
			}
			
			// start loading assets
			_asset_loader_context = new LoaderContext(true, ApplicationDomain.currentDomain, null);
			_asset_loader = new AssetLoader("assets.swf", _asset_loader_context);
			_asset_loader.addEventListener(Event.COMPLETE, onAssestLoadComplete);
			
			
			
			initIntro();
			_asset_loader.load();
			
			
			DevUtils.log("loading assets");		
		}
		
		private function initIntro() : void{
			// create and set game state
			createGameStates();
			_asset_loader.addEventListener(BulkLoader.PROGRESS, _intro_screen_view.onLoadingSWF);
		}

		// -- methods -------------------------------------------------------------------------------
		public function onAssestLoadComplete(e:Event):void 
		{
			/// for Debugging Only
			// status text
			if(DEBUG){
				_status_tf = TextFieldGenerator._getDefaultTextField("Started\n", stage.stageWidth - 10, 56, 14, false, false);
				_status_tf.mouseEnabled = false;
				_status_tf.x = 5;
				_status_tf.y = 35;
				stage.addChild(_status_tf);
			}
			
			
			// create players
			_players = new Array();
			var p0:Player = new Player(0, "", this, new Rectangle(0, 0, 					  stage.stageWidth/2 - 50, stage.stageHeight));
			var p1:Player = new Player(1, "", this, new Rectangle(stage.stageWidth/2 + 50, 0, stage.stageWidth/2 - 50, stage.stageHeight));
			_players.push(p0);
			_players.push(p1);
			_current_player = p0; // set current player
		}
		
		private function createGameStates():void
		{
			_game_states = new Object();
			_game_states[Game.START_SCREEN]  	= new GameStateStartScreen(this);
			_game_states[Game.GAME_SELECTION]  	= new GameStateGameSelection(this);
			_game_states[Game.DEFAULT] 		 	= new GameStateDefault(this);
			_game_states[Game.ADD_BODY] 	 	= new GameStateAddBody(this);
			_game_states[Game.MODIFY_BODIES] 	= new GameStateModifyBodies(this);
			_game_states[Game.INTRO_SCREEN]		= new GameStateIntroScreen(this);
			_game_states[Game.SETTINGS_SCREEN]	= new GameStateSettingsScreen(this);
			
			//_state = _game_states[Game.START_SCREEN];
			_state = _game_states[Game.INTRO_SCREEN];
			_state.init();
		}
		
		public function buildStartScreenView():void
		{
			if(_start_screen_view == null)
				_start_screen_view = new StartScreenView(this);
			_start_screen_view.show();
			addChild(_start_screen_view);
		}
		public function buildSettingsScreenView():void
		{
			if(_settings_screen_view == null)
				_settings_screen_view = new GameSettingsView(this);
			_settings_screen_view.show();
			addChild(_settings_screen_view);
		}
		
		public function buildIntroScreenView():void
		{
			if(_intro_screen_view == null)
				_intro_screen_view = new IntroView(this);
			_intro_screen_view.show();
			addChild(_intro_screen_view);
		}
		
		
		public function buildGameSelectionView():void
		{
			if(_game_selection_view == null)
				_game_selection_view = new GameSelectionView(this);
			_game_selection_view.show();
			addChild(_game_selection_view);
		}
		
		public function resetPlayersAction():void{
			for (var i:int=0; i<_players.length; ++i){
				(_players[i] as Player).resetActions();
			}
		}
		public function buildInGameView():void
		{
			
			if(_in_game_view == null)
				_in_game_view = new InGameView(this);
			_in_game_view.show();
			_in_game_view.setActivePlayer(_current_player.id);
			addChild(_in_game_view);
			
		}
		
		public function hideInGameView():void
		{
			_in_game_view.hide();
		}
		
		public function getGameState(state:String):IGameState
		{
			if (_game_states[state] == undefined || _game_states[state] == null)
				return _game_states[Game.DEFAULT];
			trace("state"+_game_states[state]);
			return _game_states[state]; 
		}
		
		
		public function setPlayerData(player0_name:String, player1_name:String):void
		{
			trace("setPlayerData");
			_players[0].name = player0_name;
			_players[1].name = player1_name;
		}
		
		public function executeActionOnCurrentPlayer():void
		{
			_current_player.executAction();
		}
		
		public function startGame(game_type:String):void
		{	
			_is_paused = false;
			
			// set game type
			_game_type = game_type;
			
			// start game
			createGameWorld();
			
			// activate first player
			_current_player.activate();
			
			resetPlayersAction();
			resetIconsList();
			logStatus("Current Player is '" + _current_player.name + "'");			
		}
		
		public function pauseGame():void
		{
			_is_paused = true;
		}
		
		public function resetIconsList():void{
			_in_game_view.drawIconsList();
		}
		
		public function updatePlayerHeights():void{
			_in_game_view.updateHeights();
		}
		
		public function showSwapPlayerMsg(): void {
			_in_game_view.showSwapPlayerMsg();
		}
		
		public function hideSwapPlayerMsg(): void {
			_in_game_view.hideSwapPlayerMsg();
		}

		public function createGameWorld():void
		{
			// create Physical Game World and start simulating
			_world = new GameWorld(this, stage.stageWidth, stage.stageHeight, new b2Vec2(0, 15), 20, _asset_loader);
			_world.startSimulation();
			
			
			stage.addChild(_world);
			
			// create objects listener
			stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseUp);
			stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
		}
		
		public function resetGameWorld():void
		{
			_players[0].reset();
			_players[1].reset()
			_world.removeAllBodies();
		}
		
		public function swapPlayers():void
		{
			checkForWinner();
			
			_current_player.deactivate();
			
			if (_current_player.id == 0)
				_current_player = _players[1];
			else
				_current_player = _players[0];
			
			_current_player.activate();
				
			_in_game_view.setActivePlayer(_current_player.id);
			resetIconsList();
			updatePlayerHeights();
			logStatus("Changed current Player to: " + _current_player.name);
		}
		
		public function logStatus(text:String):void
		{
			if(DEBUG){
				_status_tf.appendText(text + "\n");
				_status_tf.scrollV = _status_tf.maxScrollV;
			}
		}
		
		private function onMouseUp(e:MouseEvent):void {
			_state.onMouseUp(e);
		}
		
		private function onKeyUp(e:KeyboardEvent):void 
		{
			if (_is_paused) return;
			
			switch (e.keyCode) 
			{
				case 49:
					changeStateToDefault();
					break;
					
				case 50:
					changeStateToAddBody();
					break;
					
				case 51:
					changeStateToModifyBodies();
					break;
					
				case Keyboard.ENTER:
					_state.changeStateToAddBody();
					_in_game_view.hideSwapPlayerMsg();
					_state.swapPlayers();
					break;
					
				default :
					break;
			}
		}
		
		public function addBodyToCurrentPlayer():void
		{
			var asset_class_name:String = "PhysBox";
			if (_current_player.id == 0)
				asset_class_name += "Red";
			else
				asset_class_name += "Blue";
				
			_current_player.addBody( _world.addBox(asset_class_name, 30, 30, 10, 10) );
		}
		
		public function addBodyToOppositePlayer(player_id:int, body : IGameBody) : void
		{
			// - restore checkPositions enter frame after change?
			var restoreState:Boolean = false;
			if (_world.check_positions)
			{
				restoreState = true;
				_world.stopBoxPositionListener();
			}
			
			// - add new body to player
			var from_player:Player = getPlayerById(player_id);
			var to_player:Player = getNextPlayer(player_id);
			
			// fetch & update asset of body (aka "change color")
			var asset_class_name:String = "PhysBox";
			if ( to_player.id == 0){
				asset_class_name += "Red";
			} else {
				asset_class_name += "Blue";
			}
			  // update view(asset)
			body.changeAssetTo( _asset_loader.getDefinitionOf(asset_class_name) );		
			
			// remove body from current_player body list
			from_player.removeBodyFromPlayer(body);
			
			// add to tmp:players body list
			to_player.addBody(body.body);	
			
			// - restore checkPositions enter frame after change if neceassary
			if(restoreState)
			{
				_world.startBoxPositionListener();
			}
		}

		
		public function checkForWinner(player:Player = null):void
		{
			if (player == null)
				player = _current_player;
			
			if (player.checkForWin(_world, stage.stageHeight - _in_game_view.getWinnerLinePosition().y))
				_state.onPlayerWon(player);
		}
		
		public function getPlayerByMousePosition():Player
		{
			var i:int = 0;
			var l:int = _players.length;
			for (; i < l; ++i)
			{
				if ( Player(_players[i]).area.containsPoint(new Point(stage.mouseX, stage.mouseY)) )
					return Player(_players[i]);
			}
			
			return _current_player;
		}
		
		/*
		 * returns the next Player base either on current_id if specified or on game._current_player if < 0
		 */
		public function getNextPlayer(current_id:int = -1):Player
		{
			if (current_id < 0)
				current_id = _current_player.id;
			
			var next_player_id : int = (current_id + 1);
			if (next_player_id % _players.length == 0)
				next_player_id = 0;
			
			return getPlayerById(next_player_id);
		}
		
		public function getPlayerById(id:int):Player
		{
			var i:int = 0;
			var l:int = _players.length;
			for (; i < l; ++i)
			{
				if ( Player(_players[i]).id == id )
					return Player(_players[i]);
			}
			
			return null;
		}
		
		public function isPlayerAllowedToExecuteAction():Boolean
		{
			if (_game_type == Game.GAME_TYPE_A) return true;
			
			return (getPlayerByMousePosition() == _current_player)
		}
		
		public function onPlayerWon(player:Player):void
		{
			_in_game_view.showWinnerScreen(player);
			pauseGame();
		}
		
		public function enableDragAndDrop():void {
			_current_player.enableDragAndDrop();
		}
		
		public function disableDragAndDrop():void {
			_current_player.disableDragAndDrop();
		}
		
		public function changeStateToStartScreen():void {
			_state.changeStateToStartScreen();
		}
		
		public function changeStateToSettingsScreen():void {
			trace("changestatetosettingsscreen");
			_state.changeStateToSettingsScreen();
		}
		
		public function changeStateToGameSelection():void {
			_state.changeStateToGameSelection()
		}
		
		public function changeStateToDefault():void {
			_state.changeStateToDefault();
		}
		
		public function changeStateToAddBody():void {
			_state.changeStateToAddBody();
		}
		
		public function changeStateToModifyBodies():void {
			_state.changeStateToModifyBodies()
		}
		
		public function quit():void {
			_state.quit();
		}
		
		
	}
	
}